﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Battleship
{
    public class LoadingPlan
    {
        public string filePath = "";
        public iExcelReader excelReader;

        public List<ContainerInfo> containerList;

        public List<ContainerInfo> ORDList, REFList, DANList, EMPList;
        public List<ContainerInfo> ORD_20_List, ORD_40_List,
                                REF_20_List, REF_40_List,
                                DAN_20_List, DAN_40_List,
                                EMP_20_List, EMP_40_List;

        public LoadingSpace loadingSpace;
        public PortList portList;

        public Moment moment;
        public double minMX, minMY, minMZ;
        public double m;
        // METHOD
        public LoadingPlan()
        {
            excelReader = new iExcelReader("C:\\myData.xlsx");
            containerList = new List<ContainerInfo>();
            ORDList = new List<ContainerInfo>();
            REFList = new List<ContainerInfo>();
            DANList = new List<ContainerInfo>();
            EMPList = new List<ContainerInfo>();
            ORD_20_List = new List<ContainerInfo>();
            ORD_40_List = new List<ContainerInfo>();
            REF_20_List = new List<ContainerInfo>();
            REF_40_List = new List<ContainerInfo>();
            DAN_20_List = new List<ContainerInfo>();
            DAN_40_List = new List<ContainerInfo>();
            EMP_20_List = new List<ContainerInfo>();
            EMP_40_List = new List<ContainerInfo>();
            loadingSpace = new LoadingSpace();
            portList = new PortList();

            moment = new Moment(0, 0, 0);
            minMX = 0;
            minMY = 0;
            minMZ = 0;
            m = 0;
        }
        public void readFile(string path)
        {
            // READ FILE
            excelReader = new iExcelReader(path);
            portList = excelReader.excel_readToPort();
            containerList = excelReader.excel_readToContainer(portList);
            excelReader.excel_close();
        }
        public void splitList()
        {
            List<ContainerInfo> firstPortList = new List<ContainerInfo>();
            List<ContainerInfo> secondPortList = new List<ContainerInfo>();

            // Build container list in separate port
            foreach (ContainerInfo item in containerList)
            {
                if (item.POO.PI == 0)
                {
                    firstPortList.Add(item);
                }
                else
                {
                    secondPortList.Add(item);
                }
            }

            // Split
            foreach (ContainerInfo item in firstPortList)
            {
                String type = item.Type;
                switch (type)
                {
                    case "ORD":
                        if (item.Size == 20)
                            ORD_20_List.Add(item);
                        else
                            ORD_40_List.Add(item);
                        break;
                    case "REF":
                        if (item.Size == 20)
                            REF_20_List.Add(item);
                        else
                            REF_40_List.Add(item);
                        break;
                    case "DAN":
                        if (item.Size == 20)
                            DAN_20_List.Add(item);
                        else
                            DAN_40_List.Add(item);
                        break;
                    case "EMP":
                        if (item.Size == 20)
                            EMP_20_List.Add(item);
                        else
                            EMP_40_List.Add(item);
                        break;
                    default:
                        break;
                }
            }
        }

        // FILL
        public void fillFirstTier_REFZ()
        {

            // ---------- ()
            Navigation root = new Navigation(0, 0, 0, 1, 0, 3);
            for (int i = 0; i < 8; i++)
            {
                pushContainer(root, ORD_20_List);
                bool result = root.Next();
                if (result == false)
                {
                    break;
                }
            }
        }
        public void fill_REFZ()
        {
            // Estimate number of Tier for 20ft
            int count = REF_20_List.Count;
            int conPerTier = 2 * loadingSpace.numRow;
            int numFullTier = count / conPerTier;


            Navigation root;

            // fill all numFullTier with 20ft
            if (numFullTier > 0)
            {
                root = new Navigation(1, numFullTier, 0, 1, 0, 3);
                for (int i = 0; i < numFullTier * conPerTier; i++)
                {
                    pushContainer(root, REF_20_List);
                    bool result = root.Next();
                    if (result == false)
                    {
                        break;
                    }
                }
            } // else, fill by separated zone 20ft vs 40ft

            // 20ft
            root = new Navigation(numFullTier + 1, 3, 0, 1, 0, 1);
            int numLoop = REF_20_List.Count;
            for (int i = 0; i < numLoop; i++)
            {
                pushContainer(root, REF_20_List);

                bool result = root.Next();
                if (result == false)
                {
                    break;
                }
            }
            // 40ft
            root = new Navigation(numFullTier + 1, 3, 0, 0, 2, 3);
            numLoop = REF_40_List.Count;
            for (int i = 0; i < numLoop; i++)
            {
                pushContainer(root, REF_40_List);

                bool result = root.Next();
                if (result == false)
                {
                    break;
                }
            }
        }
        public void fill_DANZ()
        {
            // Estimate number of Tier for 20ft
            int count = DAN_20_List.Count;
            int conPerTier = 2 * loadingSpace.numRow;
            int numFullTier = count / conPerTier;


            Navigation root;

            // fill all numFullTier with 20ft
            if (numFullTier > 0)
            {
                root = new Navigation(0, numFullTier - 1, 6, 7, 0, 3);
                for (int i = 0; i < numFullTier * conPerTier; i++)
                {
                    pushContainer(root, DAN_20_List);

                    bool result = root.Next();
                    if (result == false)
                    {
                        break;
                    }
                }
            } // else, fill by separated zone 20ft vs 40ft

            // 20ft
            root = new Navigation(numFullTier, 3, 6, 7, 0, 1);
            int numLoop = DAN_20_List.Count;
            for (int i = 0; i < numLoop; i++)
            {
                pushContainer(root, DAN_20_List);
                bool result = root.Next();
                if (result == false)
                {
                    break;
                }
            }
            // 40ft
            root = new Navigation(numFullTier, 3, 6, 6, 2, 3);
            numLoop = DAN_40_List.Count;
            for (int i = 0; i < numLoop; i++)
            {
                pushContainer(root, DAN_40_List);
                bool result = root.Next();
                if (result == false)
                {
                    break;
                }
            }
        }
        public void fill_ORDZ()
        {
            // Estimate number of Tier for 20ft
            int count = ORD_20_List.Count;
            int conPerTier = 4 * loadingSpace.numRow;
            int numFullTier = count / conPerTier;

            Navigation root;
            if (numFullTier >= 5)
            {
                numFullTier = 4;
            }

            // fill all numFullTier with 20ft
            if (numFullTier > 0)
            {

                root = new Navigation(0, numFullTier - 1, 2, 5, 0, 3);
                for (int i = 0; i < numFullTier * conPerTier; i++)
                {
                    pushContainer(root, ORD_20_List);

                    bool result = root.Next();
                    if (result == false)
                    {
                        break;
                    }
                }
            } // else, fill by separated zone 20ft vs 40ft

            // 20ft
            if (numFullTier <= 3)
            {

                root = new Navigation(numFullTier, 3, 2, 5, 0, 1);
                int numLoop = ORD_20_List.Count;
                for (int i = 0; i < numLoop; i++)
                {
                    pushContainer(root, ORD_20_List);

                    bool result = root.Next();
                    if (result == false)
                    {
                        break;
                    }
                }
            }
            // 40ft
            if (numFullTier <= 3)
            {
                root = new Navigation(numFullTier, 3, 2, 5, 2, 3);
                int numLoop = ORD_40_List.Count;
                for (int i = 0; i < numLoop; i++)
                {
                    if (pushContainer(root, ORD_40_List))
                    {
                        bool result = root.Next(2);
                        if (result == false)
                        {
                            break;
                        }
                    }

                }
            }
        }
        public void fill_ORDZ_cont()
        {
            int ord20 = ORD_20_List.Count;
            int ord40 = ORD_40_List.Count;
            bool result;
            if (ord20 != 0)
            {
                Navigation root = new Navigation(0, 3, 0, 7, 0, 1);
                do
                {
                    pushContainer(root, ORD_20_List);
                    result = root.Next();
                } while (result == true);
            }
            if (ord40 != 0)
            {
                Navigation root = new Navigation(0, 3, 0, 7, 2, 3);
                do
                {
                    pushContainer(root, ORD_40_List);
                    result = root.Next(2);
                } while (result);
            }

        }
        public void push_EMP()
        {
            int emp20 = EMP_20_List.Count;
            int emp40 = EMP_40_List.Count;
            bool result;
            if (emp20 != 0)
            {
                Navigation root = new Navigation(0, 4, 0, 7, 0, 3);
                do
                {
                    pushContainer(root, EMP_20_List);
                    result = root.Next();
                } while (result);
            }
            if (emp40 != 0)
            {
                Navigation root = new Navigation(0, 4, 0, 7, 2, 3);
                do
                {
                    pushContainer(root, EMP_40_List);
                    result = root.Next(2);
                } while (result);
            }
        }
        public bool pushContainer(Navigation root, List<ContainerInfo> list)
        {
            if (list.Count == 0)
            {
                return false;
            }
            else
            {
                ContainerInfo item = list[0];
                int t = root.cursor.tier;
                int b = root.cursor.bay;
                int r = root.cursor.row;
                if (t < loadingSpace.numTier && b < loadingSpace.numBay && r < loadingSpace.numRow)
                {
                    if (loadingSpace.content[t, b, r].container == null)
                    {
                        list.RemoveAt(0);
                        item.GCor = new Cor(new SlotPoint(t, b, r));
                        item.valMoment();
                        loadingSpace.content[t, b, r].container = item;

                        if (item.Size == 40)
                        {
                            ContainerInfo itemTail = new ContainerInfo();
                            itemTail = item;
                            itemTail.GCor = new Cor(new SlotPoint(t, b + 1, r));
                            itemTail.valMoment();
                            itemTail.isTail = true;
                            loadingSpace.content[t, b + 1, r].container = itemTail;
                        }
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                    return false;

            }

        }

        // LOADING
        public bool load()
        {
            // PREPARE
            splitList();

            // PUSH 4 ORD-CON INTO REF-ZONE
            fillFirstTier_REFZ();

            // FILL THE REF-ZONE
            fill_REFZ();

            // FILL THE DAN-ZONE
            fill_DANZ();

            // FILL THE ORD-ZONE
            fill_ORDZ();

            fill_ORDZ_cont();

            // PUSH EMPTY CONTAINER
            push_EMP();

            return true;
        }

        // VAL MOMENT
        public void valMoment()
        {
            moment = loadingSpace.valMoment();
        }

        // BALANCE
        public bool vertBal()
        {
            if (loadingSpace.valMoment().X > 0) return true;
            for (int i = 0; i < 5; i++)
            {
                List<ContainerSlot> ordList = loadingSpace.getListOrd20OfTier(i);
                ordList.Sort(Sort.slotPTypeCpr);

            }
            return true;
        }
        public bool isSwapOK(ContainerSlot A, ContainerSlot B)
        {
            if (A.container != null && B.container != null)
            {
                if (A.container.Size == B.container.Size)
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }
            else if (A.container == null && B.container != null)
            {
                if (B.container.Size == 40)
                {
                    int t = A.location.tier;
                    int b = A.location.bay;
                    int r = A.location.row;
                    if (b == 1 || b == 3 || b == 5 || b == 7)
                    {
                        return false;
                    }
                    if (loadingSpace.content[t, b + 1, r].container == null)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return true;
                }
            }
            else if (B.container == null && A.container != null)
            {
                if (A.container.Size == 40)
                {
                    int t = B.location.tier;
                    int b = B.location.bay;
                    int r = B.location.row;
                    if (b == 1 || b == 3 || b == 5 || b == 7)
                    {
                        return false;
                    }
                    if (loadingSpace.content[t, b + 1, r].container == null)
                    {
                        return true;
                    }
                    else
                        return false;
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return false;
            }

        }


        public void balBF_X(int tierNum)
        {
            minMX = loadingSpace.valMoment().X;
            //balTier(tierNum);
            List<ContainerSlot> ordSlotList = loadingSpace.getListOrd20OfTier(tierNum);
            foreach (ContainerSlot item in ordSlotList)
            {
                List<ContainerSlot> ordSlotListTmp = ordSlotList;
                foreach (ContainerSlot itemTmp in ordSlotListTmp)
                {
                    loadingSpace.swap(item.location, itemTmp.location);
                    double momentXTmp = loadingSpace.valMoment().X;
                    if (Math.Abs(momentXTmp) < Math.Abs(minMX))
                    {
                        minMX = momentXTmp;
                    }
                    else
                    {
                        loadingSpace.swap(item.location, itemTmp.location);
                    }
                }
            }
        }
        public void balBF_Y(int tierNum)
        {
            minMY = loadingSpace.valMoment().Y;
            balTier(tierNum);
            List<ContainerSlot> ordSlotList = loadingSpace.getListOrd20OfTier(tierNum);
            foreach (ContainerSlot item in ordSlotList)
            {
                List<ContainerSlot> ordSlotListTmp = ordSlotList;
                foreach (ContainerSlot itemTmp in ordSlotListTmp)
                {
                    loadingSpace.swap(item.location, itemTmp.location);
                    double momentYTmp = loadingSpace.valMoment().Y;
                    if (Math.Abs(momentYTmp) < Math.Abs(minMY))
                    {
                        minMX = momentYTmp;
                    }
                    else
                    {
                        loadingSpace.swap(item.location, itemTmp.location);
                    }
                }
            }
        }
        public void balTier(int tierNum)
        {
            List<ContainerSlot> ordSlotList = loadingSpace.getListOrd20OfTier(tierNum);
            List<ContainerSlot> ordSlotListIndex = loadingSpace.getAllSlot20OfTier(tierNum);

            // sort ordSlotLis by Weight
            ordSlotList.Sort(Sort.slotWeightCpr);

            // sort ordSlotListIndex by PType
            ordSlotListIndex.Sort(Sort.slotPTypeCpr);

            foreach (ContainerSlot maxPType in ordSlotListIndex)
            {
                if (ordSlotList.Count != 0)
                {
                    ContainerSlot maxWeight = ordSlotList[0];
                    if (maxPType.container != null)
                    {
                        if ((maxPType.container.Weight < maxWeight.container.Weight) && isSwapOK(maxPType, maxWeight))
                        {
                            swap(maxPType, maxWeight);
                            ordSlotList.RemoveAt(0);
                        }
                    }
                    else
                    {
                        if (isSwapOK(maxPType, maxWeight))
                        {
                            swap(maxPType, maxWeight);
                            ordSlotList.RemoveAt(0);
                        }
                    }

                }
            }

        }
        public void swap(ContainerSlot A, ContainerSlot B)
        {
            SlotPoint pA = A.location;
            SlotPoint pB = B.location;
            loadingSpace.swap(pA, pB);
        }


        public string checkRule()
        {
            string result = "";
            //first port
            //Port firstPort = portfindPort("0");
            List<ContainerInfo> tmp = new List<ContainerInfo>();
            foreach (ContainerInfo item in containerList)
            {
                if (item.POO.PI == 0)
                {
                    tmp.Add(item);
                }
            }

            int fullCon1 = 0;
            int emptyCon1 = 0;

            foreach (ContainerInfo item in tmp)
            {
                if (item.Type.Equals("EMP"))
                {
                    if (item.Size.Equals("20"))
                    {
                        emptyCon1 += 1;
                    }
                    else
                    {
                        emptyCon1 += 2;
                    }

                }
                else
                {
                    if (item.Size.Equals("20"))
                    {
                        fullCon1 += 1;
                    }
                    else
                    {
                        fullCon1 += 2;
                    }

                }
            }


            //second port
            //Port secondPort = findPort("1");
            //Port thirdPort = findPort("2");
            tmp = new List<ContainerInfo>();
            foreach (ContainerInfo item in containerList)
            {
                if (item.POO.PI == 1)
                {
                    tmp.Add(item);
                }
                else if (item.POD.PI == 2)
                {
                    tmp.Add(item);
                }
            }

            int fullCon2 = 0;
            int emptyCon2 = 0;

            foreach (ContainerInfo item in tmp)
            {
                if (item.Type.Equals("EMP"))
                {
                    if (item.Size.Equals("20"))
                    {
                        emptyCon2 += 1;
                    }
                    else
                    {
                        emptyCon2 += 2;
                    }

                }
                else
                {
                    if (item.Size.Equals("20"))
                    {
                        fullCon2 += 1;
                    }
                    else
                    {
                        fullCon2 += 2;
                    }

                }
            }

            // report
            result += "\n:::: Port 0 : " + portList.content[0].PN + " ::::::::::::::::::::::::::::::::::::::::::::\n";
            result += "Total : \t\t\t" + (emptyCon1 + fullCon1) + " TEU\n";
            result += "Empty container : \t\t" + emptyCon1 + " TEU\n";
            result += "Non-empty container : \t" + fullCon1 + " TEU\n";
            result += ">>>> ";
            if ((emptyCon1 + fullCon1) > 128)
            {
                result += "ERROR : Container loading fail \n(containers size > 128 TEU)\n";
            }
            else if (fullCon1 > 96)
            {
                result += "ERROR : Container loading fail \n(Non-empty containers size > 96 TEU)\n";
            }
            else if (emptyCon1 > 32)
            {
                result += "WARNING : empty containers size > 32 TEU\n";
            }
            else
            {
                result += "Container loading succesfull\n";
            }



            result += "\n:::: Port 1 : " + portList.content[1].PN + " ::::::::::::::::::::::::::::::::::::::::::::\n";
            result += "Total : \t\t\t" + (emptyCon2 + fullCon2) + " TEU\n";
            result += "Empty container: \t\t" + emptyCon2 + " TEU\n";
            result += "Non-empty container : \t" + fullCon2 + " TEU\n";
            result += ">>>> ";
            if ((emptyCon2 + fullCon2) > 128)
            {
                result += "ERROR : Container loading fail \n(containers size > 128 TEU)\n";
            }
            else if (fullCon2 > 96)
            {
                result += "ERROR : Container loading fail \n(Non-empty containers size > 96 TEU)\n";
            }
            else if (emptyCon2 > 32)
            {
                result += "WARNING : empty containers size > 32 TEU\n";
            }
            else
            {
                result += "Container loading succesfull\n";
            }
            return result;
        }

        public void addContainer(ContainerInfo container)
        {
            this.containerList.Add(container);
        }
        public void myMethod()
        {
            // do it
        }

        public void optimize()
        {
            for (int j = 0; j < loadingSpace.numTier; j++)
            {
                balTier(j);
            }
            for (int i = 0; i < loadingSpace.numTier; i++)
            {
                balBF_X(i);
            }
        }
        public void run()
        {
            readFile(filePath);

            splitList();

            // PUSH 4 ORD-CON INTO REF-ZONE
            fillFirstTier_REFZ();

            // FILL THE REF-ZONE
            fill_REFZ();

            // FILL THE DAN-ZONE
            fill_DANZ();

            // FILL THE ORD-ZONE
            fill_ORDZ();

            fill_ORDZ_cont();

            // PUSH EMPTY CONTAINER
            push_EMP();

            // OPTIMIZE
            optimize();
        }
        public double getM()
        {
            double m = 0;
            foreach (ContainerInfo item in containerList)
            {
                m += item.Weight;
            }
            return m;
        }
        public void topdown()
        {
            for (int tier = 0; tier < 4; tier++)
            {
                for (int bay = 0; bay < 8; bay++)
                {
                    for (int row = 0; row < 4; row++)
                    {
                        ContainerSlot containerSlotUp = this.loadingSpace.content[tier + 1, bay, row];
                        ContainerSlot containerSlotDown = this.loadingSpace.content[tier, bay, row];
                        if (containerSlotDown.container==null && containerSlotUp.container!=null)
                        {
                            swap(containerSlotUp, containerSlotDown);
                        }
                    }
                }
            }
        }

        public ContainerInfo getContainerInfo(ContainerModel containerModel)
        {
            int tier = containerModel.position.y;
            int bay = containerModel.position.z;
            int row = containerModel.position.x;

            return this.loadingSpace.content[tier, bay, row].container;
        }
        public bool firstSwapable(ContainerInfo A, ContainerInfo B)
        {
            if ((A.Type != B.Type)||(A.Size != B.Size))
            {
                ;
            }
            return true;
        }
        public int getInHold()
        {
            int ondeck = 0;
            int inhold = 0;
            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    for (int k = 0; k < 4; k++)
                    {
                        ContainerInfo containerInfo = loadingSpace.content[i, j, k].container;
                        if (containerInfo != null)
                        {
                            if (i <= 1)
                            {
                                inhold++;
                            }
                            else
                            {
                                ondeck++;
                            }
                        }
                    }
                }
            }
            return inhold;
        }
        public int getOnDeck()
        {
            int ondeck = 0;
            int inhold = 0;
            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    for (int k = 0; k < 4; k++)
                    {
                        ContainerInfo containerInfo = loadingSpace.content[i, j, k].container;
                        if (containerInfo != null)
                        {
                            if (i <= 1)
                            {
                                inhold++;
                            }
                            else
                            {
                                ondeck++;
                            }
                        }
                    }
                }
            }
            return ondeck;
        }
        public void swap(ContainerModel cM1, ContainerModel cM2)
        {
            SlotPoint slotPoint1 = posToSlotPoint(cM1.position);
            SlotPoint slotPoint2 = posToSlotPoint(cM2.position);

            ContainerSlot conSlot1 = loadingSpace.content[slotPoint1.tier, slotPoint1.bay, slotPoint1.row];
            ContainerSlot conSlot2 = loadingSpace.content[slotPoint2.tier, slotPoint2.bay, slotPoint2.row];

            swap(conSlot1, conSlot2);
        }
        public SlotPoint posToSlotPoint(Position pos)
        {
            // H
            SlotPoint result = new SlotPoint(pos.x, pos.y, pos.z);
            return result;
        }


    }
}
